跳到主要内容

Linux 中的服务与初始化系统

初始化系统的概念

在 Linux中,无论何时当你安装任何带有服务和守护进程的包,系统默认会把这些服务的初始化及 systemd 脚本添加进去,不过此时它们并没有被启用。

检查当前系统使用的初始化系统

ps --no-headers -o comm 1

我们需要手动的开启或者关闭那些服务。Linux 中有三个著名的且一直在被使用的初始化系统。

  • System V(Sys V)即 init
  • Upstart
  • systemd

以 System V 为例,启动时它会去启动 init 进程,init 是内核引导系统启动过程中第一个启动的进程(BIOS、MBR、GRUB 和内核程序在启动 init 之前就作为 Linux 的引导程序的一部分开始工作了);这个 init 进程的 PID 是1,除非系统关机否则它将会一直在后台运行;它首先根据 /etc/inittab 文件(Debian 没有这玩意)决定 Linux 运行的级别,然后根据运行级别在后台启动所有其他进程和应用程序。

下面是 Linux 中可以使用的运行级别(从0~6总共七个运行级别):

0:关机 1:单用户模式 2:多用户模式(没有NFS) 3:完全的多用户模式 4:系统未使用 5:图形界面模式 6:重启

Debian 系的有点不同,具体看 这里

可以通过检查进程的方式来判断当前 Linux 的初始化系统是什么,如下 PID 1 对应的进程是 init,所以使用的是 Sys V 初始化系统

当前版本

$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 900 528 ? Sl 12:06 0:00 /init
root 113 0.0 0.0 900 84 ? Ss 12:06 0:00 /init
root 114 0.0 0.0 900 84 ? S 12:06 0:00 /init
...

System V(Sys V)

System V(Sys V)是类 Unix 系统第一个也是传统的初始化系统。启动时它会先去执行 init,init 是内核引导系统启动过程中第一支启动的程序,它是所有其他进程的直接或间接的父进程。

Init 是一个守护进程,它将持续运行,直到系统关闭。

因为 init 的参数全在 /etc/init.d 目录下

$ ll /etc/init.d
total 128K
-rwxr-xr-x 1 root root 3.7K Apr 1 2020 apparmor
-rwxr-xr-x 1 root root 2.9K Dec 7 2019 apport
-rwxr-xr-x 1 root root 1.1K Jul 24 2018 atd
-rwxr-xr-x 1 root root 1.3K Mar 27 2020 console-setup.sh
-rwxr-xr-x 1 root root 3.0K Feb 11 2020 cron
-rwxr-xr-x 1 root root 937 Feb 4 2020 cryptdisks
-rwxr-xr-x 1 root root 896 Feb 4 2020 cryptdisks-early
...

所以使用 init 启动一个服务,应该这样做:

$ sudo /etc/init.d/nginx start

或者使用 service

$ sudo service mongod start

service 命令

service 是一个运行 System V init 的脚本命令。也就是 /etc/init.d 目录下的参数,所以分析可知 service 是去 /etc/init.d 目录下执行相关程序,服务配置文件的存放目录就是 /etc/init.d

# 查看所有服务当前的运行状态
service --status-all

# 查看指定服务(vsftpd)的运行状态
service vsftpd status

# 停止指定服务(vsftpd)
service vsftpd stop

# 重启网络服务
service network restart

检查当前的服务

$ service --status-all
[ - ] apparmor
[ ? ] apport
[ + ] atd
[ - ] console-setup.sh
[ + ] cron
...
  • [+] 代表服务是在启动运行的状态
  • [-] 代表服务是在关闭停止的状态systemctl 命令
  • [?] 这个状态标识服务在初始化或者启动时没有输出有效的状态

使用 service 启动一个服务

$ service nginx start

可以理解成 service 就是 init.d 的一种实现方式。 所以这两者启动方式(或者是停止、重启)并没有什么区别。

$ sudo /etc/init.d/nginx start
# 等价于
$ service nginx start

但是这两种方式均有如下缺点:

  • 启动时间长:init 进程是串行启动,只有前一个进程启动完,才会启动下一个进程。
  • 启动脚本复杂:init 进程只是执行启动脚本,不管其他事情,脚本需要自己处理各种情况,这往往使得脚本变得很长。

init.d 服务启动脚本编写

参考 ubuntu 下 init.d 服务启动脚本编写

Upstart

Upstart 被用于 Ubuntu 9.10 到 Ubuntu 14.10 和基于 RHEL 6 的系统,之后它被 systemd 取代。

Upstart 是一个基于事件的 /sbin/init 守护进程的替代品,它在系统启动过程中处理任务和服务的启动,在系统运行期间监视它们,在系统关机的时候关闭它们;它最初是为 Ubuntu 而设计,但是它也能够完美的部署在其他所有 Linux 系统中,用来代替古老的 System-V。

systemd ⭐

Systemd 是 Linux 系统中最新的初始化系统(init),它主要的设计目的是克服 System V init 固有的缺点(看上面 service 命令里描述的缺点),提高系统的启动速度。它包括 System and Service Manager,为系统的启动和管理提供一套完整的解决方案。

根据 Linux 惯例,字母 d 是守护进程(daemon)的缩写。 Systemd 这个名字的含义,就是它要守护整个系统。

使用了 Systemd,就不需要再用 init 了。Systemd 取代了 initd(Initd 的 PID 是0) ,成为系统的第一个进程(Systemd 的PID 等于 1),其他进程都是它的子进程。

所以可知

  • init 是最初的进程管理方式
  • service 是init 的另一种实现
  • systemd 则是一种取代 initd 的解决方案

Systemd 的优点是功能强大,使用方便,缺点是体系庞大,非常复杂。

查看 Systemd 的版本信息

$ systemctl --version

systemd 245 (245.4-4ubuntu3.11)
+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=hybrid

如下 systemd 初始化系统去检查运行级别:

$ systemctl get-default
graphical.target

$ ls -al /lib/systemd/system/runlevel*
lrwxrwxrwx 1 root root 15 Jul 22 2021 /lib/systemd/system/runlevel0.target -> poweroff.target
lrwxrwxrwx 1 root root 13 Jul 22 2021 /lib/systemd/system/runlevel1.target -> rescue.target
lrwxrwxrwx 1 root root 17 Jul 22 2021 /lib/systemd/system/runlevel2.target -> multi-user.target
lrwxrwxrwx 1 root root 17 Jul 22 2021 /lib/systemd/system/runlevel3.target -> multi-user.target
lrwxrwxrwx 1 root root 17 Jul 22 2021 /lib/systemd/system/runlevel4.target -> multi-user.target
lrwxrwxrwx 1 root root 16 Jul 22 2021 /lib/systemd/system/runlevel5.target -> graphical.target
lrwxrwxrwx 1 root root 13 Jul 22 2021 /lib/systemd/system/runlevel6.target -> reboot.target

可以看到当前是第 5 级

Systemd 并不是一个命令,而是一组命令,涉及到系统管理的方方面面。

systemctl

systemctl 是 Systemd 的主命令,用于管理系统。

# 重启系统
$ sudo systemctl reboot

# 启动进入救援状态(单用户状态)
$ sudo systemctl rescue

# 管理服务
$ sudo systemctl start nginx

hostnamectl

hostnamectl 命令用于查看当前主机的信息。

# 显示当前主机信息
$ hostnamectl

# 设置主机名
$ sudo hostnamectl set-hostname BoodeUbuntu

localectl

localectl 命令用于查看本地化设置。

# 查看本地化设置
$ localectl

# 设置本地化参数。
$ sudo localectl set-locale LANG=en_GB.utf8
$ sudo localectl set-keymap en_GB

timedatectl

timedatectl 命令用于查看当前时区设置。

# 查看当前时区设置
$ timedatectl

# 显示所有可用的时区
$ timedatectl list-timezones

# 设置当前时区
$ sudo timedatectl set-timezone America/New_York
$ sudo timedatectl set-time YYYY-MM-DD
$ sudo timedatectl set-time HH:MM:SS

systemd 自行编写服务

Systemd 服务是一种以 .service 结尾的单元(unit)配置文件,用于控制由Systemd 控制或监视的进程。简单说,用于后台以守护精灵(daemon)的形式运行程序。

Systemd Service 位于 /etc/systemd/system(供系统管理员和用户使用),/usr/lib/systemd/system(供发行版打包者使用),我们一般使用前者即可。

Systemd 服务的内容主要分为三个部分,控制单元(unit)的定义、服务(service)的定义、以及安装部分。

应用安装在 systemd 里面

$ ll /etc/systemd/system
total 80K
drwxr-xr-x 2 root root 4.0K Aug 20 2021 cloud-final.service.wants
drwxr-xr-x 2 root root 4.0K Aug 20 2021 cloud-init.target.wants
lrwxrwxrwx 1 root root 44 Aug 20 2021 dbus-org.freedesktop.resolve1.service -> /lib/systemd/system/systemd-resolved.service
lrwxrwxrwx 1 root root 45 Aug 20 2021 dbus-org.freedesktop.timesync1.service -> /lib/systemd/system/systemd-timesyncd.service
drwxr-xr-x 2 root root 4.0K Aug 20 2021 default.target.wants
drwxr-xr-x 2 root root 4.0K Aug 20 2021 final.target.wants
drwxr-xr-x 2 root root 4.0K Aug 20 2021 getty.target.wants
drwxr-xr-x 2 root root 4.0K Aug 20 2021 graphical.target.wants
lrwxrwxrwx 1 root root 38 Aug 20 2021 iscsi.service -> /lib/systemd/system/open-iscsi.service
drwxr-xr-x 2 root root 4.0K Aug 20 2021 mdmonitor.service.wants
drwxr-xr-x 2 root root 4.0K Aug 20 2021 multi-user.target.wants
lrwxrwxrwx 1 root root 38 Aug 20 2021 multipath-tools.service -> /lib/systemd/system/multipathd.service
drwxr-xr-x 2 root root 4.0K Aug 20 2021 network-online.target.wants
drwxr-xr-x 2 root root 4.0K Aug 20 2021 open-vm-tools.service.requires
drwxr-xr-x 2 root root 4.0K Aug 20 2021 paths.target.wants
-rw-r--r-- 1 root root 285 Aug 20 2021 snap-core18-2128.mount
-rw-r--r-- 1 root root 279 Aug 20 2021 snap-lxd-21029.mount
-rw-r--r-- 1 root root 285 Aug 20 2021 snap-snapd-12704.mount
-rw-r--r-- 1 root root 467 Aug 20 2021 snap.lxd.activate.service
-rw-r--r-- 1 root root 541 Aug 20 2021 snap.lxd.daemon.service
-rw-r--r-- 1 root root 330 Aug 20 2021 snap.lxd.daemon.unix.socket
drwxr-xr-x 2 root root 4.0K Aug 20 2021 sockets.target.wants
lrwxrwxrwx 1 root root 31 Aug 20 2021 sshd.service -> /lib/systemd/system/ssh.service
drwxr-xr-x 2 root root 4.0K Aug 20 2021 sysinit.target.wants
lrwxrwxrwx 1 root root 35 Aug 20 2021 syslog.service -> /lib/systemd/system/rsyslog.service
drwxr-xr-x 2 root root 4.0K Aug 20 2021 timers.target.wants
lrwxrwxrwx 1 root root 41 Aug 20 2021 vmtoolsd.service -> /lib/systemd/system/open-vm-tools.service

定义控制单元 Unit

在 Systemd 中,所有引导过程中 Systemd 要控制的东西都是一个单元。基本的用法如下:

  1. Description:代表整个单元的描述,可根据需要任意填写。
  2. Wants:本单元启动了,它 “想要” 的单元也会被启动。但是这个单元若启动不成功,对本单元没有影响。
  3. Requires: 这个单元启动了,那么它 “需要” 的单元也会被启动;它 “需要” 的单元被停止了,它自己也活不了。但是请注意,这个设定并不能控制启动顺序,因为它 “需要” 的单元启动也需要时间,若它 “需要” 的单元启动还未完成,就开始启动本单元,则本单元也无法启动,所以不建议使用这个字段。
  4. OnFailure:若本单元启动失败了,那么启动这个单元作为折衷。
  5. Before/After:指定启动顺序。

e.g.

[Unit]
Description=Protect ARP list
Wants=network-online.target
After=network.target

其中 network.target 代表有网路,network-online.target 代表一个连通着的网络。

定义服务本体 service

在定义完了 Systemd 用来识别服务的单元后,我们来定义服务本体。基本的用法如下:

Type:服务的类型,各种类型的区别如下所示

  • simple:默认,这是最简单的服务类型。意思就是说启动的程序就是主体程序,这个程序要是退出那么一切皆休。
  • forking:标准 Unix Daemon 使用的启动方式。启动程序后会调用 fork() 函数,把必要的通信频道都设置好之后父进程退出,留下守护精灵的子进程。
  • oneshot:适用于那些被一次性执行的任务或者命令,它运行完成后便了无痕迹。因为这类服务运行完就没有任何痕迹,我们经常会需要使用 RemainAfterExit=yes。意思是说,即使没有进程存在,Systemd 也认为该服务启动成功了。同时只有这种类型支持多条命令,命令之间用;分割,如需换行可以用。
  • dbus:这个程序启动时需要获取一块 DBus 空间,所以需要和 BusName= 一起用。只有它成功获得了 DBus 空间,依赖它的程序才会被启动。

ExecStart:在输入的命令是 start 时候执行的命令,这里的命令启动的程序必须使用绝对路径,比如你必须用 /sbin/arp 而不能简单的以环境变量直接使用arp。

ExecStop:在输入的命令是 stop 时候执行的命令,要求同上。

ExecReload:这个不是必需,如果不写则你的 service 就不支持 restart 命令。ExecStart 和 ExecStop 是必须要有的。

e.g.

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/arp -f /etc/ip-mac
ExecReload=/sbin/arp -f /etc/ip-mac
ExecStop=/sbin/arp -d -a

这里在 start 和 restart 的时候会读取并添加 /etc/ip-mac 文件中的 ARP 条目到 ARP 表中,而 stop 时清空 ARP 表。

安装服务 install

服务编写完之后还需要被 systemd 装载,定义安装单元各个字段如下:

WantedBy:设置服务被谁装载,一般设置为 multi-user.target Alias:为 service 设置一个别名,可以使用多个名字来操作服务。 Also:在安装这个服务时候还需要的其他服务

完整的配置实例

组合上面的三个模块,我们可以得到一个完整的 Systemd Service 配置实例:

[Unit]
Description=Protect ARP list
Wants=network-online.target
After=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/arp -f /etc/ip-mac
ExecReload=/sbin/arp -f /etc/ip-mac
ExecStop=/sbin/arp -d -a

[Install]
WantedBy=multi-user.target

References

ubuntu中systemctl和service Linux服务管理(详解)! controlling targets - runlevels with systemd linux systemd自行编写服务 如何编写一个Systemd Service